package com.example;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class MyApplicationContext {

    private Class<?> classObj;
    private ConcurrentHashMap<String,Object> singletonLists = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();

    private List<Processor> processorLists = Collections.synchronizedList(new ArrayList<>());

    public MyApplicationContext(Class classObj) {
        // 找路径，包扫描，注入
        // 传入进来一个ApplyConfig， 用于包名的扫描工作
        scan(classObj);
        // 封装单例池， 便于直接获取单例bean， singletonLists.get()获取即可
        useSingletonPool();
    }

    private final void useSingletonPool() {
        for (Map.Entry<String,BeanDefinition> m: beanDefinitionMap.entrySet()) {
            String className = m.getKey();
            BeanDefinition beanDefinition = m.getValue();
            if(beanDefinition.getScope().equals("singleton")) {
                // 往单例池中写东西
                Object obj = createBean(className, beanDefinition);
                singletonLists.put(className, obj);
            }
        }
    }

    private Object createBean(String className, BeanDefinition beanDefinition) {
        // className用于干其他事情

        Class<?> clazz = beanDefinition.getClazz();

        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();

            // Autowired
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if(declaredField.isAnnotationPresent(Autowired.class)) {
//                    String name = declaredField.getName();
                    Class<?> type = declaredField.getType();
                    String simpleName = type.getSimpleName();
                    String name = simpleName.substring(0,1).toLowerCase()+simpleName.substring(1);
                    Object bean = getBean(name);
                    declaredField.setAccessible(true);
                    declaredField.set(instance, bean);
                }
            }

            // BeanNameAware
            if(instance instanceof BeanNameAware) {
                ((BeanNameAware)instance).setBeanName(className);
            }

            for (Processor processorList : processorLists) {
                instance = processorList.postProcessBeforeInitialization(instance, className);
            }

            for (Processor processorList : processorLists) {
                instance = processorList.postProcessAfterInitialization(instance, className);
            }
            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    private void scan(Class classObj) {
        this.classObj = classObj;
        if(classObj.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) classObj.getDeclaredAnnotation(ComponentScan.class);
            String packageName = componentScan.value();
            String newPackagePath = packageName.replace(".", "/");
            // 获取到包名后需要动态加载ClassLoader
            ClassLoader classLoader = this.getClass().getClassLoader();
            try {
                URL resources = classLoader.getResource(newPackagePath);
                String file = resources.getFile();
                List<String> fileList = new ArrayList<>();
                getFileLists(packageName, file, fileList);
                for (String beanPackageClass : fileList) {
                    Class<?> clazz = classLoader.loadClass(beanPackageClass);
                    // 判断上方是否存@Service， @Component注解
                    if(clazz.isAnnotationPresent(Component.class)) {
                        // 获取类的别名
                        String componentName = clazz.getDeclaredAnnotation(Component.class).value();
//
//                        Component declaredAnnotation = clazz.getDeclaredAnnotation(Component.class);
//                        declaredAnnotation.

                        // Processor
                        if(Processor.class.isAssignableFrom(clazz)) {
                            Processor processor = (Processor) clazz.getDeclaredConstructor().newInstance();
                            processorLists.add(processor);
                        }

//                        String componentName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);

                        BeanDefinition beanDefinition = new BeanDefinition();

                        // 判断类上边是否有@Scope注解， 没有就是单例， 有就是非单例
                        if(clazz.isAnnotationPresent(Scope.class)) {
                            String scope = clazz.getDeclaredAnnotation(Scope.class).value();
                            beanDefinition.setScope(scope);
                        }else {
                            beanDefinition.setScope("singleton");  // 单例
                        }
                        beanDefinition.setClazz(clazz);

                        beanDefinitionMap.put(componentName, beanDefinition);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void getFileLists(String newPackagePath, String file, List<String> fileList) {
        File filePath = new File(file);
        if(filePath.isDirectory()) {
            File[] files = filePath.listFiles();
            for (File f : files) {
                String absolutePath = f.getAbsolutePath();
                if(absolutePath.contains(".class")) {
                    // 放入List<String>中
                    // 获取到absolutePath中 F:/a/b/c/aaa.class， 我们要a.b.c.aaa
                    String replaceAPath = absolutePath.replace("\\", ".");
                    String className = replaceAPath.substring(replaceAPath.indexOf(newPackagePath), replaceAPath.lastIndexOf(".class"));
                    fileList.add(className);
                }else{
                    // 继续遍历获取 F:/a/b/c/
                    getFileLists(newPackagePath, absolutePath, fileList);
                }
            }
        }
    }

    public Object getBean(String beanName) {
        // 判断是否存在key
        if(beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            String scope = beanDefinition.getScope();
            if(scope.equals("singleton")) {
                return singletonLists.get(beanName);
            }else {
                return createBean(beanName, beanDefinition);
            }
        }else{
            throw new RuntimeException("无Bean定义");
        }
    }
}
